
#if defined(WIN32) || defined(WIN64)
#pragma unmanaged
#endif

#include <Liberty3D/Engine/Context.h>

#include <Liberty3D/Engine/Component.h>
#include <Liberty3D/Engine/Coroutine.h>
#include <Liberty3D/Engine/GameObject.h>

using namespace Liberty::Engine;

Context::Context() :
    _running (false),
    _stopTime(0),

    _fixedDelta  (Performance::fromSeconds(0.02)),
    _maxDelta    (Performance::fromSeconds(1.0 / 3.0)),
    _maxFrameRate(Performance::fromSeconds(1.0 / 60.0)),

    _passStartTime      (0),
    _lastPassStartTime  (0),
    _lastFixedUpdateTime(0),
    _gameLoadTime       (0),
    _levelLoadTime      (0),

    _frameCount        (0),
    _renderedFrameCount(0)
{
}

Context::~Context() {
}

void Context::start() {
    if (_running) {
        return;
    }

    _running = true;
    Performance::query(_lastFixedUpdate);
    _passStartTime   = _lastPassStartTime = _lastFixedUpdate;
    _nextFixedUpdate = _lastFixedUpdate + _fixedDelta;
    _nextUpdate      = _lastFixedUpdate + _maxFrameRate;

    if (_gameLoadTime == 0) {
        _gameLoadTime = _lastFixedUpdate;
    }

    if (_levelLoadTime == 0) {
        _levelLoadTime = _lastFixedUpdate;
    }

    if (_stopTime) {
        Performance::Counter diff = _lastFixedUpdate - _stopTime;

        _gameLoadTime  += diff;
        _levelLoadTime += diff;

        _stopTime = 0;
    }

    cleanup();
}

void Context::stop() {
    cleanup();

    _running = false;
    Performance::query(_stopTime);
}

bool Context::tick(Performance::Counter& nextTick) {
    if (!_running) {
        return false;
    }

    bool                 action = false;
    Performance::Counter pass;

    Performance::query(pass);
    _passStartTime = pass;

    while (pass >= _nextFixedUpdate) {
        Performance::Counter delta = pass - _lastFixedUpdate;

        if (delta > _maxDelta) {
            _nextFixedUpdate = pass + _fixedDelta;
        }
        else {
            _nextFixedUpdate += _fixedDelta;
        }

        _lastFixedUpdateTime = _lastFixedUpdate = pass;
        fixedUpdate();

        coroutine(pass, true);
        action = true;
    }

    // Update
    if (pass >= _nextUpdate) {
        Performance::Counter delta = pass - _lastPassStartTime;

        if (delta > _maxFrameRate) {
            _nextUpdate = pass + _maxFrameRate;
        }
        else {
            _nextUpdate += _maxFrameRate;
        }

        _lastPassStartTime = pass;
        action             = true;

        update();
        animate();
        lateUpdate();
        frameRender();

        coroutine(pass, false);

        cleanup();
    }

    nextTick = _nextFixedUpdate;
    return action;
}

void Context::fixedUpdate() {
    ComponentVector::const_iterator it, end(_components.end());

    for (it = _components.begin(); it != end; ++it) {
        Component* component = (*it).get();

        if (component->calls() & FixedUpdate) {
            component->onFixedUpdate();
        }
    }
}

void Context::update() {
    ComponentVector::const_iterator it, end(_components.end());

    for (it = _components.begin(); it != end; ++it) {
        Component* component = (*it).get();

        if (component->calls() & Update) {
            component->onUpdate();
        }
    }
}

void Context::lateUpdate() {
    ComponentVector::const_iterator it, end(_components.end());

    for (it = _components.begin(); it != end; ++it) {
        Component* component = (*it).get();

        if (component->calls() & LateUpdate) {
            component->onLateUpdate();
        }
    }
}

void Context::coroutine(Performance::Counter currentTime, bool doFixedFrame) {
    CoroutineVector::iterator it, end(_coroutines.end());

    for (it = _coroutines.begin(); it != end; ++it) {
        CoroutineEntry& entry  = *it;
        CoroutineStatus status = entry.status();

        if ((doFixedFrame) && (status != WAIT_FOR_FIXED_UPDATE)) {
            continue;
        }
        else if (status == WAIT_FOR_FIXED_UPDATE) {
            continue;
        }

        if (!entry.tick(currentTime)) {
            it = _coroutines.erase(it);
        }
    }
}

template <typename Ot, typename Pred> bool IntelliUpdate(std::vector<Ot>& v, std::list<Ot>& added, std::list<Ot>& removed, Pred pred) {
    typedef typename std::list<Ot>::const_iterator   list_const_iterator;
    typedef typename std::vector<Ot>::iterator       vector_iterator;
    typedef typename std::vector<Ot>::const_iterator vector_const_iterator;
    
    list_const_iterator ait (added.begin());
    list_const_iterator aend(added.end());
    bool                sort = false;

    if (removed.begin() != removed.end()) {
        vector_iterator     it,  end(v.end());
        list_const_iterator rit, rend;

        for (it = v.begin(); it != end; ++it) {
            for (rit = removed.begin(), rend = removed.end(); rit != rend; ++rit) {
                if ((*rit) == *it) {
                    if (ait != aend) {
                        *it  = *ait;
                        sort = true;
                        ++ait;
                    }
                }
            }
        }

        removed.clear();
    }

    for (; ait != aend; ++ait) {
        v.push_back(*ait);
        sort = true;
    }

    {
        std::list<Ot> a;

        added.swap(a);

        for (ait = a.begin(), aend = a.end(); ait != aend; ++ait) {
            pred(*ait);
        }
    }

    return sort;
}

template <typename Ot> bool IntelliAdd(std::list<Ot>& added, std::list<Ot>& removed, const Ot& o) {
    typedef typename std::list<Ot>::iterator list_iterator;
    
    list_iterator end(removed.end());
    list_iterator it (std::find(removed.begin(), end, o));
    
    if (it != end) {
        removed.erase(it);
        return false;
    }

    added.push_back(o);
    return true;
}

template <typename Ot> bool IntelliRemove(std::list<Ot>& added, std::list<Ot>& removed, const Ot& o) {
    typedef typename std::list<Ot>::iterator list_iterator;
    
    list_iterator end(added.end());
    list_iterator it (std::find(added.begin(), end, o));
    
    if (it != end) {
        added.erase(it);
        return false;
    }

    removed.push_back(o);
    return true;
}

static void GameObjectUpdate(const std::shared_ptr<GameObject>&) {
}

static void ComponentUpdate(const std::shared_ptr<Component>& c) {
    c->onStart();
}

static bool ComponentSorter(const std::shared_ptr<Component>& o1, const std::shared_ptr<Component>& o2) {
    return o1->order() < o2->order();
}

void Context::cleanup() {
    IntelliUpdate<std::shared_ptr<GameObject>>(
        _gameObjects,
        _addedGameObjects,
        _removedGameObjects,
        GameObjectUpdate);

    if (IntelliUpdate<std::shared_ptr<Component>> (_components, _addedComponents, _removedComponents, ComponentUpdate)) {
        std::stable_sort(_components.begin(), _components.end(), ComponentSorter);
    }
}

void Context::addGameObject(const std::shared_ptr<GameObject>& go) {
    IntelliAdd(_addedGameObjects, _removedGameObjects, go);
}

void Context::addComponent(const std::shared_ptr<Component>& go) {
    IntelliAdd(_addedComponents, _removedComponents, go);
}

void Context::removeGameObject(const std::shared_ptr<GameObject>& go) {
    IntelliRemove(_addedGameObjects, _removedGameObjects, go);

    ComponentVector::const_iterator it, end(_components.end());

    for (it = _components.begin(); it != end; ++it) {
        const std::shared_ptr<Component>& component(*it);
        std::shared_ptr<GameObject>       parent   (component->gameObject());

        if (parent == go) {
            IntelliRemove(_addedComponents, _removedComponents, component);
        }
    }
}

void Context::removeComponent(const std::shared_ptr<Component>& go) {
    IntelliRemove(_addedComponents, _removedComponents, go);
}

std::shared_ptr<GameObject> Context::createGameObject(const std::shared_ptr<GameObject> parent, const std::string& name) {
    std::shared_ptr<GameObject> gameObject(new GameObject(shared_from_this(), name));

    if (parent) {
        gameObject->setParent(parent);
    }

    addGameObject(gameObject);
    return gameObject;
}

std::shared_ptr<Component> Context::addComponent(const std::shared_ptr<GameObject> parent, const std::shared_ptr<Component>& component) {
#ifdef _DEBUG
    {
        GameObjectList::const_iterator end(_removedGameObjects.cend());
        GameObjectList::const_iterator it (std::find(_removedGameObjects.cbegin(), end, parent));

        if (it != end) {
            throw std::runtime_error("Invalid GameObject state. GameObject is scheduled to be destroyed.");
        }
    }

    {
        GameObjectVector::const_iterator end(_gameObjects.cend());
        GameObjectVector::const_iterator it (std::find(_gameObjects.cbegin(), end, parent));

        if (it != end) {
            throw std::runtime_error("Invalid GameObject state. GameObject was destroyed.");
        }
    }
#endif

    component->_parent = parent;
    addComponent(component);
    return component;
}

void Context::animate() {
}

void Context::frameRender() {
}

void Context::handleException(const std::exception& ex) {
}
